/*
 * Copyright (c) 2022. China Mobile (SuZhou) Software Technology Co.,Ltd. All rights reserved.
 * Lakehouse is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package com.chinamobile.cmss.lakehouse.api.service.impl;

import com.chinamobile.cmss.lakehouse.api.service.UserService;
import com.chinamobile.cmss.lakehouse.common.Constants;
import com.chinamobile.cmss.lakehouse.common.enums.Status;
import com.chinamobile.cmss.lakehouse.common.enums.UserType;
import com.chinamobile.cmss.lakehouse.common.utils.ParameterUtils;
import com.chinamobile.cmss.lakehouse.common.utils.Result;
import com.chinamobile.cmss.lakehouse.dao.UserDao;
import com.chinamobile.cmss.lakehouse.dao.entity.UserEntity;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class UserServiceImpl extends BaseServiceImpl implements UserService {

    @Resource
    UserDao userDao;

    /**
     * create user
     *
     * @param loginUser    login user
     * @param userName     user name
     * @param userPassword user password
     * @param description  description
     * @return create result
     */
    @Override
    public Map<String, Object> createUser(UserEntity loginUser, String userName, String userPassword, String description) {
        Map<String, Object> result = new HashMap<>();

        // check username
        if (!checkUserName(userName)) {
            putMessage(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, userName);
            return result;
        }
        //check username exist
        if (userDao.existsByUserName(userName)) {
            putMessage(result, Status.USER_NAME_EXIST);
            return result;
        }
        // check password
        if (!checkPassword(userPassword)) {
            putMessage(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, userPassword);
            return result;
        }
        // check description
        if (!checkDescription(description)) {
            putMessage(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, description);
            return result;
        }
        if (!isAdmin(loginUser)) {
            putMessage(result, Status.USER_NO_OPERATION_PERM);
            return result;
        }

        UserEntity user = createUser(userName, userPassword, description);

        result.put(Constants.DATA_LIST, user);
        putMessage(result, Status.SUCCESS);
        return result;
    }

    /**
     * update user
     *
     * @param loginUser   login user
     * @param userId      user id
     * @param description description
     * @return update result
     */
    @Override
    public Map<String, Object> updateUser(UserEntity loginUser, String userId, String description) {
        Map<String, Object> result = new HashMap<>();
        searchUser(result, loginUser, userId);
        Status status = (Status) result.get(Constants.STATUS);
        if (status != Status.SUCCESS) {
            return result;
        }
        // check description
        if (!checkDescription(description)) {
            putMessage(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, description);
            return result;
        }
        UserEntity user = (UserEntity) result.get(Constants.DATA_LIST);
        user.setDescription(description);
        userDao.save(user);
        return result;
    }

    /**
     * delete user by id
     *
     * @param loginUser login user
     * @param userId    user  id
     * @return delete result
     */
    @Override
    public Map<String, Object> deleteUserByUserId(UserEntity loginUser, String userId) {
        Map<String, Object> result = new HashMap<>();
        //only admin can operate
        if (!isAdmin(loginUser)) {
            putMessage(result, Status.USER_NO_OPERATION_PERM, userId);
            return result;
        }
        //check exist
        Optional<UserEntity> tempUser = userDao.findByUserId(userId);
        if (!tempUser.isPresent()) {
            putMessage(result, Status.USER_NOT_EXIST, userId);
            return result;
        }
        userDao.deleteByUserId(userId);

        putMessage(result, Status.SUCCESS);

        return result;
    }

    /**
     * create user
     *
     * @param userName     user name
     * @param userPassword user password
     * @param description  description
     * @return create result
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public UserEntity createUser(String userName, String userPassword, String description) {
        UserEntity user = UserEntity.builder()
            .userName(userName)
            .userPassword(userPassword)
            .userType(UserType.GENERAL_USER)
            .description(description)
            .build();
        // save user
        userDao.save(user);
        return user;
    }

    /**
     * get user by user name
     *
     * @param userName user name
     * @return user
     */
    @Override
    public UserEntity getUserByUserName(String userName) {
        return userDao.findByUserName(userName);
    }

    /**
     * query all general user
     *
     * @param loginUser login user
     * @return general user list
     */
    @Override
    public Map<String, Object> queryAllGeneralUser(UserEntity loginUser) {
        Map<String, Object> result = new HashMap<>();
        //only admin can operate
        if (check(result, !isAdmin(loginUser), Status.USER_NO_OPERATION_PERM)) {
            return result;
        }

        List<UserEntity> userList = userDao.findByUserType(UserType.GENERAL_USER);
        result.put(Constants.DATA_LIST, userList);
        putMessage(result, Status.SUCCESS);

        return result;
    }

    /**
     * query user list
     *
     * @param loginUser login user
     * @return user list all
     */
    @Override
    public Map<String, Object> queryUserList(UserEntity loginUser) {
        Map<String, Object> result = new HashMap<>();
        //only admin can operate
        if (check(result, !isAdmin(loginUser), Status.USER_NO_OPERATION_PERM)) {
            return result;
        }

        List<UserEntity> userList = userDao.findAll();
        result.put(Constants.DATA_LIST, userList);
        putMessage(result, Status.SUCCESS);

        return result;
    }

    /**
     * query user list page
     *
     * @param loginUser login user
     * @param searchVal search value
     * @param pageNo    page number
     * @param pageSize  page size
     * @return user list page
     */
    @Override
    public Result queryUserList(UserEntity loginUser, String searchVal, Integer pageNo, Integer pageSize) {
        Result<Object> result = new Result<>();
        if (!isAdmin(loginUser)) {
            putMessage(result, Status.USER_NO_OPERATION_PERM);
            return result;
        }
        PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, Sort.Direction.DESC, "id");
        Specification<UserEntity> specification = buildSpecification(searchVal);
        Page<UserEntity> pageInfo = userDao.findAll(specification, pageRequest);
        result.setData(pageInfo);
        putMessage(result, Status.SUCCESS);
        return result;
    }

    /**
     * verify user name
     *
     * @param userName user name
     * @param userId   user id
     * @return true if user name not exists, otherwise return false
     */
    @Override
    public Result verifyUserName(String userName, String userId) {
        Result<Object> result = new Result<>();
        if (userDao.existsByUserNameAndUserIdNot(userName, userId)) {
            putMessage(result, Status.USER_NAME_EXIST);
            return result;
        }
        putMessage(result, Status.SUCCESS);
        return result;
    }

    /**
     * get user info
     *
     * @param loginUser login user
     * @param userId    user id
     * @return user info
     */
    @Override
    public Map<String, Object> getUserInfo(UserEntity loginUser, String userId) {
        Map<String, Object> result = new HashMap<>();
        searchUser(result, loginUser, userId);
        return result;
    }

    /**
     * reset password by id
     *
     * @param loginUser    login user
     * @param userId       user id
     * @param userPassword user password
     * @return reset result code
     */
    @Override
    public Map<String, Object> resetPasswordById(UserEntity loginUser, String userId, String userPassword) {
        Map<String, Object> result = new HashMap<>();
        searchUser(result, loginUser, userId);
        Status status = (Status) result.get(Constants.STATUS);
        if (status != Status.SUCCESS) {
            return result;
        }
        // check password
        if (!checkPassword(userPassword)) {
            putMessage(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, userPassword);
            return result;
        }
        UserEntity user = (UserEntity) result.get(Constants.DATA_LIST);
        user.setUserPassword(userPassword);
        userDao.save(user);
        return result;
    }

    /**
     * get user by user name and password
     *
     * @param userName     user name
     * @param userPassword user password
     * @return user
     */
    @Override
    public UserEntity getUserByUserNameAndPassword(String userName, String userPassword) {
        return userDao.findByUserNameAndUserPassword(userName, userPassword);
    }

    @Override
    public UserEntity findByUserId(String userId) {
        Optional<UserEntity> userEntity = userDao.findByUserId(userId);
        return userEntity.orElse(null);
    }

    private boolean checkUserName(String userName) {
        if (StringUtils.isEmpty(userName)) {
            return false;
        }
        return Constants.USER_NAME_PATTERN.matcher(userName).matches();
    }

    private boolean checkPassword(String password) {
        if (StringUtils.isEmpty(password)) {
            return false;
        }
        return Constants.USER_PASSWORD_PATTERN.matcher(password).matches();
    }

    private Specification<UserEntity> buildSpecification(String searchVal) {
        return (root, criteriaQuery, cBuilder) -> {
            //define a predicate
            Predicate p = cBuilder.conjunction();
            if (StringUtils.isNotBlank(searchVal)) {
                p = cBuilder.and(p, cBuilder.like(root.get("userName"), "%" + ParameterUtils.replaceSpecialChars(searchVal) + "%"));
            }
            return p;
        };
    }

    private boolean checkDescription(String description) {
        return StringUtils.isEmpty(description) || description.length() <= 200;
    }

    private void searchUser(Map<String, Object> result, UserEntity loginUser, String userId) {
        //only admin or self can operate
        if (!check(result, !isAllowed(loginUser, userId), Status.USER_NO_OPERATION_PERM)) {
            Optional<UserEntity> tempUser = userDao.findByUserId(userId);
            if (!tempUser.isPresent()) {
                putMessage(result, Status.USER_NOT_EXIST, userId);
            } else {
                putMessage(result, Status.SUCCESS);
                result.put(Constants.DATA_LIST, tempUser.get());
            }
        }
    }
}
